<template>
  <div>
    <div id="toolbar-container" :class="{'fixed': menuFixed}" :style="menuFixed ? `width: ${editorWidth}px` : ''" />
    <div id="editor" style="min-height: 800px" />
    <el-input v-if="sourceEditor" v-model="sourceHtml" class="code-source-textarea" type="textarea" autosize />
    <choose-img
      :visible.sync="chooseEditorImageDialog"
      :auto-crop-width="300"
      :auto-crop-height="300"
      :fixed="true"
      @choose="chooseEditorImage"
    />
    <choose-video
      :visible.sync="chooseEditorVideoDialog"
      @choose="chooseEditorVideo"
    />
    <el-dialog title="视频链接" :close-on-click-modal="false" :visible.sync="selectTencentVideoDialog">
      <el-input v-model="tencentVideoHtml" type="textarea" :rows="5" placeholder="请输入嵌入代码" />
      <div class="editor-form-foot">
        <el-button @click="selectTencentVideoDialog = false">取消</el-button>
        <el-button type="primary" @click="insertTecentVideoHtml">插入</el-button>
      </div>
    </el-dialog>
    <el-dialog title="图片链接" :close-on-click-modal="false" :visible.sync="insertImgAppLinkDialog">
      <el-form ref="saveForm" label-width="180px">
        <el-form-item label="图片地址">
          <el-input v-model="insertImgAppLink.imgPath" placeholder="图片地址" />
        </el-form-item>
        <el-form-item label="内链">
          <el-input v-model="insertImgAppLink.appLink" placeholder="内链" />
        </el-form-item>
      </el-form>

      <div class="editor-form-foot">
        <el-button @click="insertImgAppLinkDialog = false">取消</el-button>
        <el-button type="primary" @click="insertImgAppLinkHtml">插入</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import request from '@/utils/request'
import E from 'wangeditor';
import * as qiniu from 'qiniu-js'
import { parseTime } from '@/utils/index' // Secondary package based on el-pagination
import ChooseImg from '@/components/wejoy/ChooseImg'
import ChooseVideo from '@/components/wejoy/ChooseVideo';

export default {
  name: 'WangEditor',
  components: { ChooseImg, ChooseVideo },
  props: {
    // 根据类型初始化编辑器 默认初始化视频中台文章类型
    type: {
      type: Number,
      default: 1
    }
  },
  data: function() {
    return {
      menuFixed: false,
      editorWidth: '100',
      editor: '',
      sourceHtml: '',
      chooseEditorImageDialog: false,
      chooseEditorVideoDialog: false,
      selectTencentVideoDialog: false,
      insertImgAppLinkDialog: false,
      tencentVideoHtml: '',
      insertImgAppLink: {
        imgPath: '',
        appLink: 'v2gogo://openrouter/webView?url='
      },
      sourceEditor: false,
      isFormatPainter: false
    }
  },
  watch: {
  },
  created: function() {

  },
  mounted() {
    // 滚动条移动事件
    window.addEventListener('scroll', this.handleScroll, true);
    // 监听鼠标抬起事件
    document.getElementById('editor').addEventListener('mouseup', () => {
      // 延时调用确保富文本编辑器中的选中文本已经该改变
      setTimeout(() => {
        this.containerEleChange()
      })
    })
    if (!this.editor) {
      this.initEditor(this.type);
    }
  },
  beforeDestroy() {
    // 调用销毁 API 对当前编辑器实例进行销毁
    this.editor.destroy()
    this.editor = null
    // 移除滚动条事件
    window.removeEventListener('scroll', this.handleScroll)
    // 移除监听
    document.getElementById('editor').removeEventListener('click', this.containerEleChange)
  },
  methods: {
    initEditor(type) {
      const _this = this;
      // 自定义菜单 key ，各个菜单不能重复
      const { BtnMenu, DropListMenu } = E;
      // 定义图片资源库菜单
      class ImageResourceMenu extends BtnMenu {
        constructor(editor) {
          // data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
          const $elem = E.$(
            `<div class="w-e-menu" data-title="图片资源库">
                <i class="w-e-icon-image"></i>
            </div>`
          )
          super($elem, editor)
        }
        // 菜单点击事件
        clickHandler() {
          // 做任何你想做的事情
          // 可参考【常用 API】文档，来操作编辑器
          _this.chooseEditorImageDialog = true;
        }
        // 菜单是否被激活（如果不需要，这个函数可以空着）
        // 1. 激活是什么？光标放在一段加粗、下划线的文本时，菜单栏里的 B 和 U 被激活，如下图
        // 2. 什么时候执行这个函数？每次编辑器区域的选区变化（如鼠标操作、键盘操作等），都会触发各个菜单的 tryChangeActive 函数，重新计算菜单的激活状态
        tryChangeActive() {
          // 激活菜单
          // 1. 菜单 DOM 节点会增加一个 .w-e-active 的 css class
          // 2. this.this.isActive === true
          // this.active()

          // // 取消激活菜单
          // // 1. 菜单 DOM 节点会删掉 .w-e-active
          // // 2. this.this.isActive === false
          // this.unActive()
        }
      }
      // 编辑源代码菜单
      class sourceEditorMenu extends BtnMenu {
        constructor(editor) {
          // data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
          const $elem = E.$(
            `<div class="w-e-menu" data-title="编辑源代码" style="font-size: 15px">
                <i class="el-icon-edit"></i>
            </div>`
          )
          super($elem, editor)
        }
        // 菜单点击事件
        clickHandler() {
          _this.sourceHtml = _this.editor.txt.html();
          // 切换菜单被激活状态
          if (this.isActive) {
            this.unActive();
          } else {
            this.active()
          }
          const container = document.getElementsByClassName('w-e-text-container')[0];
          // 隐藏编辑区 显示源代码区
          if (_this.sourceEditor) {
            container.style.display = 'block';
            _this.sourceEditor = false;
          } else {
            container.style.display = 'none';
            _this.sourceEditor = true;
          }
        }
        tryChangeActive() {
        }
      }
      // 自定义标题栏
      class customEditorHeader extends DropListMenu {
        constructor(editor) {
          // 菜单栏中，标题菜单的 DOM 元素
          // 注意，这里的 $ 不是 jQuery ，是 E.$ （wangEditor 自带的 DOM 操作工具，类似于 jQuery）
          // data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
          const $elem = E.$('<div class="w-e-menu" data-title="标题 "><i class="w-e-icon-header"></i></div>')

          // droplist 配置
          const dropListConf = {
            width: 100,
            title: '设置标题',
            type: 'list',
            list: [
              { $elem: E.$('<h3>分栏标题</h3>'), value: '<h3>' },
              { $elem: E.$('<h4>标题</h4>'), value: '<h4>' },
              { $elem: E.$('<p>正文(小)</p>'), value: '<p>' }
            ],
            // droplist 每个 item 的点击事件
            clickHandler: (value) => {
              // value 参数即 dropListConf.list 中配置的 value
              const selectionText = this.editor.selection.getSelectionText();
              this.editor.cmd.do('delete', value);
              this.editor.cmd.do('insertHTML', value + `${selectionText}</` + value.replace('<', ''));
            }
          };

          super($elem, editor, dropListConf)
        }
        // 菜单是否需要激活
        tryChangeActive() {
          const reg = /^h/i
          const cmdValue = this.editor.cmd.queryCommandValue('formatBlock')
          if (reg.test(cmdValue)) {
            // 选区处于标题内，激活菜单
            this.active()
          } else {
            // 否则，取消激活
            this.unActive()
          }
        }
      }
      // 嵌入腾讯视频菜单
      class tencentVideoMenu extends BtnMenu {
        constructor(editor) {
          // data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
          const $elem = E.$(
            `<div class="w-e-menu" data-title="插入腾讯视频" style="font-size: 15px">
                <img src="https://bxgz-image.v2gogo.com/editor-tencent-icon.png" >
            </div>`
          )
          super($elem, editor)
        }
        // 菜单点击事件
        clickHandler() {
          // 切换菜单被激活状态
          if (this.isActive) {
            this.unActive();
          } else {
            this.active()
          }
          // 打开dialog
          _this.tencentVideoHtml = '';
          _this.selectTencentVideoDialog = true;
        }
        tryChangeActive() {
        }
      }
      // 嵌普通视频菜单
      class commonVideoMenu extends BtnMenu {
        constructor(editor) {
          // data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
          const $elem = E.$(
            `<div class="w-e-menu" data-title="插入视频" style="font-size: 15px">
                <img src="https://bxgz-image.v2gogo.com/editor-video-icon.png" >
            </div>`
          )
          super($elem, editor)
        }
        // 菜单点击事件
        clickHandler() {
          // 切换菜单被激活状态
          if (this.isActive) {
            this.unActive();
          } else {
            this.active()
          }
          // 打开dialog
          _this.chooseEditorVideoDialog = true;
        }
        tryChangeActive() {
        }
      }
      // 图片链接菜单
      class commonImageAppLink extends BtnMenu {
        constructor(editor) {
          // data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
          const $elem = E.$(
            `<div class="w-e-menu" data-title="插入图片链接" style="font-size: 15px">
                <img src="https://h5.v2gogo.com/lib/image-link.png?imageView2/2/w/22/h/22" >
            </div>`
          )
          super($elem, editor)
        }
        // 菜单点击事件
        clickHandler() {
          // 切换菜单被激活状态
          if (this.isActive) {
            this.unActive();
          } else {
            this.active()
          }
          // 打开dialog
          _this.insertImgAppLinkDialog = true;
        }
        tryChangeActive() {
        }
      }
      // 左对齐
      class justifyLeftMenu extends BtnMenu {
        constructor(editor) {
          // data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
          const $elem = E.$(
            `<div class="w-e-menu" data-title="左对齐">
                <i class="w-e-icon-paragraph-left"></i>
            </div>`
          )
          super($elem, editor)
        }
        // 菜单点击事件
        clickHandler() {
          _this.doJustify(_this.editor, 'left');
        }
        tryChangeActive() {
        }
      }
      // 居中对齐
      class justifyCenterMenu extends BtnMenu {
        constructor(editor) {
          // data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
          const $elem = E.$(
            `<div class="w-e-menu" data-title="居中对齐">
                <i class="w-e-icon-paragraph-center"></i>
            </div>`
          )
          super($elem, editor)
        }
        // 菜单点击事件
        clickHandler() {
          _this.doJustify(_this.editor, 'center');
        }
        tryChangeActive() {
        }
      }
      // 右对齐
      class justifyRightMenu extends BtnMenu {
        constructor(editor) {
          // data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
          const $elem = E.$(
            `<div class="w-e-menu" data-title="右对齐">
                <i class="w-e-icon-paragraph-right"></i>
            </div>`
          )
          super($elem, editor)
        }
        // 菜单点击事件
        clickHandler() {
          _this.doJustify(_this.editor, 'right');
        }
        tryChangeActive() {
        }
      }
      // 两端对齐
      class justifyMenu extends BtnMenu {
        constructor(editor) {
          // data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
          const $elem = E.$(
            `<div class="w-e-menu" data-title="两端对齐">
                <i class="w-e-icon-paragraph-justify"></i>
            </div>`
          )
          super($elem, editor)
        }
        // 菜单点击事件
        clickHandler() {
          _this.doJustify(_this.editor, 'justify');
        }
        tryChangeActive() {
        }
      }
      // 清除格式
      class clearFormatMenu extends BtnMenu {
        constructor(editor) {
          const $elem = E.$(
            `<div class="w-e-menu"  data-title="清除格式">
                <i class="el-icon-delete"></i>
              </div>`,
          )
          super($elem, editor)
        }
        clickHandler() {
          // 选区内容是否为空
          const isSelectionEmpty = this.editor.selection.isSelectionEmpty();
          if (isSelectionEmpty) {
            return;
          }
          // 选区内容html
          const selectionDomHtml = this.editor.selection.getSelectionContainerElem().elems[0].outerHTML;
          // 选区内容文字
          const selectionText = this.editor.selection.getSelectionText();
          let editorContent = this.editor.txt.html();
          editorContent = editorContent.replace(selectionDomHtml, `<p>${selectionText}</p>`);
          this.editor.txt.html(editorContent);
        }
        tryChangeActive() {}
      }

      /**
       * 文字颜色选择器,
       * 复制并修改自源码font-color/index.ts
       * (原来的逻辑是增加<font>标签, 现在是修改当前元素的style)
       */
      class fontColorPickerMenu extends DropListMenu {
        constructor(editor) {
          const $elem = E.$(
            `<div class="w-e-menu" data-title="文字颜色">
                <i class="w-e-icon-pencil2"></i>
            </div>`
          )
          const colorListConf = {
            width: 120,
            title: '文字颜色',
            // droplist 内容以 block 形式展示
            type: 'inline-block',
            list: editor.config.colors.map(color => {
              return {
                $elem: E.$(`<i style="color:${color};" class="w-e-icon-pencil2"></i>`),
                value: color
              }
            }),
            clickHandler: (value) => {
              // this 是指向当前的 BackColor 对象
              this.command(value)
            }
          }
          super($elem, editor, colorListConf)
        }

        /**
         * 执行命令
         * (修改源码)
         * @param value value
         */
        command(value) {
          const editor = this.editor
          const isEmptySelection = editor.selection.isSelectionEmpty()
          let $selectionElem = null
          if (editor.selection.getSelectionContainerElem()) {
            $selectionElem = editor.selection.getSelectionContainerElem().elems[0]
          }

          if ($selectionElem == null) {
            return
          }
          // 获取选区范围的文字
          const $selectionText = editor.selection.getSelectionText()
          if ($selectionElem.nodeName === 'A' && $selectionElem.textContent === $selectionText) {
            // 创建一个相当于占位的元素
            const _payloadElem = E.$(`<span style="color: ${value} !important">${$selectionText}</span>`).getNode()
            // 添加到a标签之后
            $selectionElem.appendChild(_payloadElem)
          } else {
            this.editor.cmd.do('insertHTML', `<span style="color: ${value} !important">${$selectionText}</span>`);
          }

          if (isEmptySelection) {
            // 需要将选区范围折叠起来
            editor.selection.collapseRange()
            editor.selection.restoreSelection()
          }
        }

        /**
         * 尝试修改菜单激活状态
         */
        tryChangeActive() {}
      }
      // 自定义格式刷
      class formatPainterMenu extends BtnMenu {
        constructor(editor) {
          // data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
          const $elem = E.$(
            `<div class="w-e-menu" data-title="格式刷">
                        <i class="el-icon-s-open"></i>
                    </div>`
          )
          super($elem, editor)
        }
        // 菜单点击事件，获取点击格式刷后的下一次点击文本
        clickHandler() {
          _this.isFormatPainter = !_this.isFormatPainter;
          if (_this.isFormatPainter) {
            this.active();
          } else {
            this.unActive();
            return;
          }
          const nodeArray = []
          // 获取当前选中元素所有父样式
          function getAllStyle(dom) {
            if (!dom) return
            const tagName = dom.tagName
            if (tagName === 'p' || tagName === 'P') {
              nodeArray.push({
                tagName: 'span',
                attributes: Array.from(dom.attributes).map((i) => {
                  return {
                    name: i.name,
                    value: i.value
                  }
                })
              })
              return
            } else {
              nodeArray.push({
                tagName: tagName,
                attributes: Array.from(dom.attributes).map((i) => {
                  return {
                    name: i.name,
                    value: i.value
                  }
                })
              })
            }
            return nodeArray
          }
          // 获取鼠标位置的节点
          const containerEle = this.editor.selection.getSelectionStartElem()
            .elems[0]
          const brushStyle = getAllStyle(containerEle)
          if (brushStyle) {
            // 有复制到的格式就开启格式刷
            _this.ifBrushed = true // 格式刷开启
            _this.brushStyle = brushStyle // 格式刷样式存储
          }
        }
        // 菜单是否被激活（如果不需要，这个函数可以空着）
        tryChangeActive() {
        }
      }
      // 两端对齐
      class textIndentMenu extends BtnMenu {
        constructor(editor) {
          // data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
          const $elem = E.$(
            `<div class="w-e-menu" data-title="首行缩进" style="z-index:auto;">
             <i class="el-icon-s-fold"></i>
          </div>`
          );
          super($elem, editor);
        }
        // 菜单点击事件
        clickHandler(value) {
          const editor = this.editor;
          const $elems = editor.selection.getSelectionRangeTopNodes(); // $elems——选中完整段落或多个段落——上级节点
          if ($elems.length > 0) {
            $elems.forEach(item => {
              // operateElement($(item), value, editor)
              const $elem = item.getNodeTop(editor); // 获取选中的上一级节点
              const reg = /^(P|H[0-9]*)$/; // 判断是不是p\h标签
              // getNodeName获取标签名
              if (reg.test($elem.getNodeName())) {
                const $elem1 = $elem.elems[0]; // 获取每一行的带标签内容
                if ($elem1.style['textIndent'] === '') {
                  // 如果此时的样式没有缩进，就添加缩进样式
                  $elem.css('text-indent', '2em');
                } else {
                  // 如果此时的样式有缩进，就取消缩进样式
                  $elem.css('text-indent', '');
                }
              }
            });
          }
          // 恢复选区
          editor.selection.restoreSelection();
          this.tryChangeActive(); // 菜单高亮
        }
        tryChangeActive() {
          // 激活菜单
          // 1. 菜单 DOM 节点会增加一个 .w-e-active 的 css class
          // 2. this.this.isActive === true
          // this.active()
          const editor = this.editor;
          const $selectionElem = editor.selection.getSelectionStartElem(); // 获取选中的dom信息
          const $selectionStartElem = $selectionElem.getNodeTop(editor); // 获取选中的上级节点信息
          if ($selectionStartElem.length <= 0) return; // 如果没有上级节点就止步于此
          if ($selectionStartElem.elems[0].style['textIndent'] !== '') {
            // 如果有样式，就激活菜单
            // active 、unActive方法是官方提供的菜单激活方法
            this.active();
          } else {
            this.unActive();
          }
        }
      }

      const imgMenuKey = 'imageResourceKey';
      const sourceEditorMenuKey = 'sourceEditorMenuKey';
      const customHeaderKey = 'customHeaderKey';
      const tencentVideoMenuKey = 'tencentVideoMenuKey';
      const commonVideoMenuKey = 'commonVideoMenuKey';
      const commonImageAppLinkKey = 'commonImageAppLink';
      const justifyLeftMenuKey = 'justifyLeftMenuKey';
      const justifyCenterMenuKey = 'justifyCenterMenuKey';
      const justifyRightMenuKey = 'justifyRightMenuKey';
      const justifyMenuKey = 'justifyMenuKey';
      const clearFormatMenuKey = 'clearFormatMenuKey';
      const fontColorPicker = 'fontColorPicker';
      const formatPainterMenuKey = 'formatPainterMenuKey';
      const textIndentMenuKey = 'textIndentMenuKey';

      _this.editor = new E('#toolbar-container', '#editor');
      // 注册菜单
      _this.editor.menus.extend(imgMenuKey, ImageResourceMenu);
      _this.editor.menus.extend(sourceEditorMenuKey, sourceEditorMenu);
      _this.editor.menus.extend(customHeaderKey, customEditorHeader);
      _this.editor.menus.extend(tencentVideoMenuKey, tencentVideoMenu);
      _this.editor.menus.extend(commonVideoMenuKey, commonVideoMenu);
      _this.editor.menus.extend(commonImageAppLinkKey, commonImageAppLink);
      _this.editor.menus.extend(justifyLeftMenuKey, justifyLeftMenu);
      _this.editor.menus.extend(justifyCenterMenuKey, justifyCenterMenu);
      _this.editor.menus.extend(justifyRightMenuKey, justifyRightMenu);
      _this.editor.menus.extend(justifyMenuKey, justifyMenu);
      _this.editor.menus.extend(clearFormatMenuKey, clearFormatMenu);
      _this.editor.menus.extend(formatPainterMenuKey, formatPainterMenu);
      _this.editor.menus.extend(fontColorPicker, fontColorPickerMenu);
      _this.editor.menus.extend(textIndentMenuKey, textIndentMenu);
      // 配置菜单栏菜单
      if (type === 9) {
        _this.editor.config.menus = [
          customHeaderKey,
          // 'head',
          'fontSize',
          'italic',
          'underline',
          'strikeThrough',
          'indent',
          'lineHeight',
          fontColorPicker,
          'backColor',
          'link',
          'list',
          // 'justify',
          justifyLeftMenuKey,
          justifyCenterMenuKey,
          justifyRightMenuKey,
          justifyMenuKey,
          'quote',
          'image',
          imgMenuKey,
          'splitLine',
          'undo',
          'redo',
          sourceEditorMenuKey,
          clearFormatMenuKey,
          fontColorPicker,
          textIndentMenuKey
          // formatPainterMenuKey
        ];
        // 配置粘贴文本的内容处理
        _this.editor.config.pasteTextHandle = this.earlyBusPasteTextHandle;
      } else {
        _this.editor.config.menus = [
          'head',
          'bold',
          'fontSize',
          'italic',
          'underline',
          'strikeThrough',
          'indent',
          'lineHeight',
          fontColorPicker,
          'backColor',
          'link',
          'list',
          // 'justify',
          justifyLeftMenuKey,
          justifyCenterMenuKey,
          justifyRightMenuKey,
          justifyMenuKey,
          'quote',
          'image',
          imgMenuKey,
          tencentVideoMenuKey,
          commonVideoMenuKey,
          commonImageAppLinkKey,
          'splitLine',
          'undo',
          'redo',
          sourceEditorMenuKey,
          clearFormatMenuKey,
          fontColorPicker,
          textIndentMenuKey
          // formatPainterMenuKey
        ];
        // 配置粘贴文本的内容处理
        _this.editor.config.pasteTextHandle = this.bxgzInfoPasteTextHandle;
      }
      _this.commonEditorConfig(_this);
      // 自定义上传功能
      _this.editor.config.customUploadImg = this.uploadSingleImg;
      _this.editor.create();
      this.$emit('init', _this.editor);
    },
    chooseEditorImage(file) {
      const imagePath = this.cfg.imageDomain + file.path;
      const src = `<img src="${imagePath}" style="max-width:100%;" alt="图片" contenteditable="false">`;
      this.editor.cmd.do(
        'insertHTML',
        src
      );
      this.editor.initSelection(true);
      this.chooseEditorImageDialog = false;
    },
    chooseEditorVideo(file) {
      const videoPath = this.cfg.imageDomain + file.path;
      const src = `<video src="${videoPath}" style="max-width:100%;" controls></video>`;
      this.editor.cmd.do(
        'insertHTML',
        src
      );
      this.editor.initSelection(true);
      this.chooseEditorVideoDialog = false;
    },
    insertTecentVideoHtml() {
      this.tencentVideoHtml = this.tencentVideoHtml.replace('<iframe', '<iframe style="width:100%"');
      this.editor.cmd.do(
        'insertHTML',
        this.tencentVideoHtml
      );
      this.editor.initSelection(true);
      // this.editor.txt.append(this.tencentVideoHtml);
      this.selectTencentVideoDialog = false;
    },
    insertImgAppLinkHtml() {
      const html =
          `
          <a href="${this.insertImgAppLink.appLink}">
              <img style="width: 100%" src="${this.insertImgAppLink.imgPath}" href="${this.insertImgAppLink.appLink}" >
          </a>
        `
      this.editor.cmd.do('insertHTML', html);
      this.editor.initSelection(true);
      // this.editor.txt.append(this.tencentVideoHtml);
      this.insertImgAppLinkDialog = false;
    },
    guid() {
      function S4() {
        return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
      }

      return (S4() + S4() + S4() + S4() + S4() + S4() + S4() + S4());
    },
    handleScroll() {
      const scrollOffset = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
      // 顶部有默认高度165, menu高度是85, 超过后就需要吸顶
      if (scrollOffset > 165 + 85) {
        this.menuFixed = true;
        this.editorWidth = document.getElementById('editor').offsetWidth;
      } else {
        this.menuFixed = false;
      }
    },
    // 截图 复制粘贴单图上传
    uploadSingleImg(resultFiles, insertImgFn) {
      const _this = this;
      // resultFiles 是 input 中选中的文件列表
      // insertImgFn 是获取图片 url 后，插入到编辑器的方法
      request({
        method: 'post',
        url: '/file/upload/getUploadToken'
      }).then(
        res => {
          resultFiles.forEach(file => {
            const nowDate = parseTime(new Date(), '{y}/{m}/{d}');
            const key = `editor/${nowDate}/${_this.guid()}.png`;
            const observable = qiniu.upload(file, key, res.data, {}, {
              cdnUphost: _this.cfg.qiniuDomain
            })
            observable.subscribe(next => {
              console.log('next %o', next);
            }, err => {
              console.log('error %o', err);
            }, completed => {
              console.log('completed %o', completed);
              insertImgFn(_this.cfg.imageDomain + '/' + completed.key);
            })
          })
        }
      )
    },
    earlyBusPasteTextHandle(pasteStr) {
      const _this = this;
      // 对粘贴的文本进行处理，然后返回处理后的结果
      // 针对底部多出来的换行进行替换
      pasteStr = pasteStr.replaceAll('<br/></p>', '</p>')
      // 替换strong标签
      pasteStr = pasteStr.replaceAll('<strong>', '');
      pasteStr = pasteStr.replaceAll('</strong>', '');
      pasteStr = pasteStr.replaceAll('<b>', '');
      pasteStr = pasteStr.replaceAll('</b>', '');
      pasteStr = pasteStr.replaceAll(/<font[^>]*>/g, '');
      pasteStr = pasteStr.replaceAll('</font>', '');
      pasteStr = pasteStr.replaceAll(/<span[^>]*>/g, '');
      pasteStr = pasteStr.replaceAll('</span>', '');
      pasteStr = pasteStr.replaceAll(/<div[^>]*>/g, '');
      pasteStr = pasteStr.replaceAll('</div>', '');
      pasteStr = pasteStr.replaceAll(/<blockquote[^>]*>/g, '');
      pasteStr = pasteStr.replaceAll('</blockquote>', '');
      pasteStr = pasteStr.replaceAll(/<lable>/g, '<span>');
      pasteStr = pasteStr.replaceAll('</lable>', '</span>');
      // 替换h1标签 -> 标题
      pasteStr = pasteStr.replaceAll('<h1>', '<h4>');
      pasteStr = pasteStr.replaceAll('</h1>', '</h4>');
      // 替换h2标签 -> 标题
      pasteStr = pasteStr.replaceAll('<h2>', '');
      pasteStr = pasteStr.replaceAll('</h2>', '');
      // 替换h3标签 -> 空, 有些情况会导致h3标签包了过多内容,导致样式乱, 暂定让他们手动添加
      pasteStr = pasteStr.replaceAll('<h3><p>', '')
      pasteStr = pasteStr.replaceAll('</p></h3>', '')
      pasteStr = pasteStr.replaceAll('<h3>', '');
      pasteStr = pasteStr.replaceAll('</h3>', '');
      pasteStr = pasteStr.replaceAll('<h5>', '');
      pasteStr = pasteStr.replaceAll('</h5>', '');
      pasteStr = pasteStr.replaceAll('<h4>', '');
      pasteStr = pasteStr.replaceAll('</h4>', '');
      // 替换section标签
      pasteStr = pasteStr.replaceAll(/<section[^>]*>/g, '<p>');
      pasteStr = pasteStr.replaceAll('</section>', '</p>');
      // 过滤所有样式
      pasteStr = pasteStr.replaceAll(/style=".+"/g, '');
      pasteStr = pasteStr.replaceAll(/size="\d+"/g, '');
      pasteStr = pasteStr.replaceAll(/<a.*">/g, '')
      pasteStr = pasteStr.replaceAll('</a>', '')
      return _this.commonUploadPasteImg(pasteStr);
    },
    bxgzInfoPasteTextHandle(pasteStr) {
      const _this = this;
      // 对粘贴的文本进行处理，然后返回处理后的结果
      // 替换strong标签
      pasteStr = pasteStr.replaceAll('<strong>', '');
      pasteStr = pasteStr.replaceAll('</strong>', '');
      // 替换section标签
      pasteStr = pasteStr.replaceAll(/<section[^>]*>/g, '<p>');
      pasteStr = pasteStr.replaceAll('</section>', '</p>');
      // 替换label标签
      pasteStr = pasteStr.replaceAll(/<label[^>]*>/g, '<span>');
      pasteStr = pasteStr.replaceAll('</label>', '</span>');
      pasteStr = pasteStr.replaceAll(/<label>/g, '<span>');
      pasteStr = pasteStr.replaceAll('</label>', '</span>');
      pasteStr = pasteStr.replaceAll(/<lable>/g, '<span>');
      pasteStr = pasteStr.replaceAll('</lable>', '</span>');
      // 替换所有文字大小
      pasteStr = pasteStr.replaceAll(/size="\d+"/g, '');
      // 过滤所有样式
      pasteStr = pasteStr.replaceAll(/style=".+"/g, '');
      // 过滤a标签
      pasteStr = pasteStr.replaceAll(/<a.*">/g, '');
      pasteStr = pasteStr.replaceAll('</a>', '');

      pasteStr = pasteStr.replaceAll(/<font[^>]*>/g, '');
      pasteStr = pasteStr.replaceAll('</font>', '');
      pasteStr = pasteStr.replaceAll(/<span[^>]*>/g, '');
      pasteStr = pasteStr.replaceAll('</span>', '');
      pasteStr = pasteStr.replaceAll(/<div[^>]*>/g, '');
      pasteStr = pasteStr.replaceAll('</div>', '');
      pasteStr = pasteStr.replaceAll(/<table[^>]*>/g, '');
      pasteStr = pasteStr.replaceAll('</table>', '');
      pasteStr = pasteStr.replaceAll(/<tbody[^>]*>/g, '');
      pasteStr = pasteStr.replaceAll('</tbody>', '');
      pasteStr = pasteStr.replaceAll(/<tr[^>]*>/g, '');
      pasteStr = pasteStr.replaceAll('</tr>', '');
      pasteStr = pasteStr.replaceAll(/<td[^>]*>/g, '');
      pasteStr = pasteStr.replaceAll('</td>', '');
      return _this.commonUploadPasteImg(pasteStr);
    },
    commonEditorConfig(_this) {
      // 设置编辑区域高度为 500px
      _this.editor.config.height = 720;
      _this.editor.config.fixed = true;
      // 设置编辑器的zIndex
      _this.editor.config.zIndex = 500;
      // 取消自动 focus
      _this.editor.config.focus = false;
      // 取消placeholder
      _this.editor.config.placeholder = '';
      // 关闭粘贴样式过滤
      _this.editor.config.pasteFilterStyle = true;
      // 配置图片上传接口地址
      // _this.editor.config.uploadImgServer = `${_this.cfg.apiDomain}/common/uploadFiles`;
      // _this.editor.config.uploadFileName = 'file';
      // 限制图片上传类型
      _this.editor.config.uploadImgAccept = ['jpg', 'jpeg', 'png', 'gif'];
      // 可使用 base64 格式保存图片。即，可选择本地图片，编辑器用 base64 格式显示图片。注意 uploadImgShowBase64不能和uploadImgServer同时使用
      _this.editor.config.uploadImgShowBase64 = false;
      // 显示/隐藏插入网络图片的功能
      _this.editor.config.showLinkImg = false;
      // 配置全屏功能按钮是否展示 注意：工具栏和编辑器区域分离的时候不支持全屏功能
      _this.editor.config.showFullScreen = true;
    },
    commonUploadPasteImg(pasteStr) {
      const _this = this;
      // 图片自动适应编辑器宽度
      var reg = /<img[^>]*>/gi;
      var res = pasteStr.match(reg);
      for (var index in res) {
        var result = res[index].replace('>', ' width="100%">');
        pasteStr = pasteStr.replace(res[index], result)
      }

      let content = pasteStr;
      // 取得内容里的所有img标签
      let imgArr = content.match(/\<img[^>]* src[^>]*\>/gi);
      // 上传图片
      if (!imgArr || imgArr.length < 1) {
        return pasteStr;
      }
      // 取得img里的src值, map成一个数组
      imgArr = imgArr.map(item => {
        let tmp = item.match(/ src\b\s*=\s*[\'\"]?([^\'\"]*)[\'\"]?/i)[0]
        tmp = tmp.substring(6, tmp.length - 1);
        return tmp;
      });

      // 将图片数组转为promise请求对象, 最后全部replace处理好后设置为content
      const uploadImgPromiseArr = [];
      var resultImg = [];
      imgArr.forEach(srcPath => {
        uploadImgPromiseArr.push(uploadImgUrl(srcPath));
      });
      Promise.all(uploadImgPromiseArr).then(res => {
        var editorContent = _this.editor.txt.html();
        console.log(editorContent);
        console.log(resultImg);
        resultImg.forEach((item) => {
          editorContent = editorContent.replace(item.source, item.target);
        });
        console.log(editorContent);
        _this.editor.txt.html(editorContent);
      });
      function uploadImgUrl(imgSrc) {
        // blob的连接要转为base64,再进行上传
        if (imgSrc.indexOf('blob') === 0) {
          return new Promise((resolve, reject) => {
            const image = new Image();
            image.onload = function() {
              const canvas = document.createElement('canvas');
              canvas.width = this.naturalWidth;
              canvas.height = this.naturalHeight;
              // 将图片插入画布并开始绘制
              canvas.getContext('2d').drawImage(image, 0, 0);
              // result
              const result = canvas.toDataURL('image/png')
              resolve(result);
            };
            // CORS 策略，会存在跨域问题https://stackoverflow.com/questions/20424279/canvas-todataurl-securityerror
            image.setAttribute('crossOrigin', 'Anonymous');
            image.src = imgSrc;
            // 图片加载失败的错误处理
            image.onerror = () => {
              reject(new Error('图片流异常'));
            };
          }).then(res => {
            return new Promise((resolve, reject) => {
              request({
                method: 'post',
                url: '/file/upload/uploadBase64',
                data: {
                  data: res
                }
              }).then(
                res => {
                  const path = res.data.key;
                  console.log(content);
                  while (content.indexOf(imgSrc) > -1) {
                    content = content.replace(imgSrc, _this.cfg.imageDomain + path);
                    resultImg.push({ source: imgSrc, target: _this.cfg.imageDomain + path });
                  }
                  resolve(content);
                }
              )
            })
          })
        }

        return new Promise((resolve, reject) => {
          const img = encodeURIComponent(imgSrc);
          request({
            method: 'post',
            url: '/file/upload/uploadUrl',
            data: {
              url: img
            }
          }).then(
            res => {
              const path = res.data;
              while (content.indexOf(imgSrc) > -1) {
                content = content.replace(imgSrc, _this.cfg.imageDomain + path);
                resultImg.push({ source: imgSrc, target: _this.cfg.imageDomain + path });
              }
              resolve(content);
            }
          )
        })
      }
      return pasteStr;
    },
    doJustify(editor, value) {
      const selection = editor.selection;
      const $selectionElem = selection.getSelectionContainerElem()
      // 保存选区
      selection.saveRange()
      // 获取顶级元素
      const $elems = editor.selection.getSelectionRangeTopNodes()
      console.log($elems);
      if ($selectionElem.length) {
        // list 在chrome下默认多包裹一个 p，导致不能通过顶层元素判断，所以单独加个判断
        if (this.isSpecialNode($selectionElem, $elems[0]) || this.isSpecialTopNode($elems[0])) {
          const el = this.getSpecialNodeUntilTop($selectionElem, $elems[0])
          if (el == null) return;
          this.css(el, 'text-align', value)
        } else {
          $elems.forEach(el => {
            this.css(el, 'text-align', value)
          })
        }
      }
      // 恢复选区
      selection.restoreSelection()
    },
    isSpecialNode(el, topEl) {
      // 如果以后有类似的元素要这样处理，直接修改这个数组即可
      const parentNode = this.getSpecialNodeUntilTop(el, topEl);
      if (parentNode == null) return false;
      return ['LI'].indexOf(parentNode.nodeName) !== -1;
    },
    isSpecialTopNode(el, topEl) {
      if (topEl == null) return false

      return ['UL', 'BLOCKQUOTE'].indexOf(topEl.elems[0].nodeName) !== -1
    },
    getSpecialNodeUntilTop(el, topEl) {
      let parentNode = el.elems[0]
      const topNode = topEl.elems[0]
      // 可能出现嵌套的情况，所以一级一级向上找，是否是特殊元素
      while (parentNode != null) {
        if (['LI'].indexOf(parentNode.nodeName) !== -1) {
          return parentNode
        }
        // 如果再到 top 元素之前还没找到特殊元素，直接返回元素
        if (parentNode.parentNode === topNode) {
          return parentNode
        }
        parentNode = parentNode.parentNode
      }
      return parentNode
    },
    /**
     * 修改 css
     * @param element
     * @param key css key
     * @param val css value
     */
    css(element, key, val) {
      let currentStyle;
      if (val === '') {
        currentStyle = ''
      } else {
        currentStyle = `${key}:${val};`
      }
      return element.forEach(elem => {
        const style = (elem.getAttribute('style') || '').trim()
        if (style) {
          // 有 style，将 style 按照 `;` 拆分为数组
          let resultArr = this.styleArrTrim(style)

          // 替换现有的 style
          resultArr = resultArr.map(item => {
            if (item.indexOf(key) === 0) {
              return currentStyle
            } else {
              return item
            }
          })
          // 新增 style
          if (currentStyle !== '' && resultArr.indexOf(currentStyle) < 0) {
            resultArr.push(currentStyle)
          }

          // 去掉 空白
          if (currentStyle === '') {
            resultArr = this.styleArrTrim(resultArr)
          }

          // 重新设置 style
          elem.setAttribute('style', resultArr.join('; '))
        } else {
          // 当前没有 style
          elem.setAttribute('style', currentStyle)
        }
      })
    },
    styleArrTrim(style) {
      let styleArr = []
      const resultArr = []

      if (!Array.isArray(style)) {
        // 有 style，将 style 按照 `;` 拆分为数组
        styleArr = style.split(';')
      } else {
        styleArr = style
      }

      styleArr.forEach(item => {
        // 对每项样式，按照 : 拆分为 key 和 value
        const arr = item.split(':').map(i => {
          return i.trim()
        })
        if (arr.length === 2) {
          resultArr.push(arr[0] + ':' + arr[1])
        }
      })
      return resultArr
    },
    /**
     * @description: 监听文本点击事件
     * @return void
     */
    containerEleChange() {
      if (!this.isFormatPainter) {
        return;
      }
      const containerEle = this.editor.selection.getSelectionContainerElem()
        .elems[0] // 选区所在的 DOM 节点
      const containerEleStart = this.editor.selection.getSelectionStartElem()
        .elems[0] // 选区开始的 DOM 节点
      const containerEleEnd = this.editor.selection.getSelectionEndElem().elems[0] // 选区结束的 DOM 节点
      const ifEmpty = this.editor.selection.isSelectionEmpty() // 判断选区是否为“空”（即没有选中任何文字）
      const containerText = this.editor.selection.getSelectionText() // 选中的文字
      // 复制style到选中的文本
      function addStyle(text, nodeArray) {
        let currentNode = null
        nodeArray.forEach((ele, index) => {
          const node = document.createElement(ele.tagName)
          for (const attr of ele.attributes) {
            node.setAttribute(attr.name, attr.value)
          }
          if (index === 0) {
            node.innerText = text
            currentNode = node
          } else {
            node.appendChild(currentNode)
            currentNode = node
          }
        })
        return currentNode
      }

      if (this.ifBrushed) {
        // 格式刷开启则开始复制style
        let containerEleNew
        let containerEleText
        if (ifEmpty) {
          // 判断选区是否为“空”（即没有选中任何文字）
          containerEleText = containerEle.innerText
          containerEleNew = addStyle(containerEle.innerText, this.brushStyle) // 新的样式
        } else {
          // 选中一段文字
          containerEleText = containerText
          containerEleNew = addStyle(containerText, this.brushStyle) // 新的样式
        }
        if (containerEleStart === containerEleEnd) {
          // 选区前后相等，选中的区域中间不夹杂其他标签
          const innerText = containerEle.innerText
          if (ifEmpty) {
            // 没有选中的文本直接全部替换掉
            containerEle.innerHTML = containerEleNew.innerHTML
            containerEle.outerHTML = containerEleNew.outerHTML
          } else {
            // 有选中的文本 暂时不做处理
            // containerEle.innerHTML = innerText.replace(
            //   containerEleText,
            //   containerEleNew.innerHTML
            // )
            containerEle.innerHTML = innerText.replace(
              containerEleText,
              containerEleText
            )
          }
        } else {
          // 选区前后不相等，选中的区域中间夹杂其他标签,操作和选区为“空”一样
          containerEleNew = addStyle(containerEle.innerText, this.brushStyle) // 新的样式
          containerEle.innerHTML = containerEleNew.innerHTML
        }
      }
      if (!this.isFormatPainter) {
        this.ifBrushed = false
      }
    }
  }
}
</script>

<style scoped lang="scss">
  #editor, #toolbar-container {
    border: 1px solid #c9d8db;
    border-bottom: 1px solid #EEE;
  }
  #toolbar-container.fixed {
    position: fixed;
    top: 0;
    z-index: 2200;
  }
  .editor-form-foot {
    text-align: center;
    margin-top: 20px;
  }
</style>
